package src

import (
	"api/models"
	"api/utils"
	"api/utils/MyRedis"
	"encoding/json"
	"strconv"
	"strings"
	"time"

	"github.com/beego/beego/v2/core/logs"
	"github.com/beego/beego/v2/core/validation"
	b "github.com/beego/beego/v2/server/web"
	"gorm.io/gorm"
	utils2 "gorm.io/gorm/utils"
)

type BaseC struct {
	b.Controller
	UserId uint32
	Filter Filter
}
type Response struct {
	Success bool        `json:"success"`
	Data    interface{} `json:"data"`
}
type Msg struct {
	Success bool   `json:"success"`
	Msg     string `json:"msg"`
}
type Table struct {
	Total int64 `json:"total"`

	TableData []map[string]interface{} `json:"tableData"`
}
type Filter struct {
	Current  int
	PageSize int
	Offset   int
	Query    map[string]interface{}
}

type ReadParams struct {
	Model  interface{}
	Field  string
	Sort   string
	Filter Filter
}

/**
status约定：
200 服务器成功返回请求的数据
400 用户请求有错误，服务器忽略此次请求。
403 用户没有权限
404 服务器没有找到请求的数据
500 服务器发生错误
*/

// Prepare 网络请求拦截器
func (c *BaseC) Prepare() {
	r := c.Ctx.Request
	ip := r.RemoteAddr // 舍弃端口号，只要IP地址。
	index := strings.LastIndex(ip, ":")
	ip = ip[0:index]
	cacheKey := ip

	// 拦截黑名单IP
	value, errMsg := MyRedis.GetData("blackIP_" + cacheKey)
	if errMsg != "" {
		c.ServerJSON(Msg{false, errMsg}, 500)
		return
	}
	if value != "" {
		c.ServerJSON(Msg{false, "黑名单IP禁止访问！"}, 403)
		return
	}

	userId := ""
	isLogin := r.URL.Path == "/api/user/login"

	if !isLogin && strings.HasPrefix(r.URL.Path, "/api/") {
		header := c.Ctx.Request.Header
		sessionName := b.BConfig.WebConfig.Session.SessionName
		userToken := header.Get(sessionName)
		// 验证用户发送来的访问令牌的有效性
		// 用户访问令牌格式：token=userId
		// 例如：newToken=1
		if len(userToken) < 10 {
			c.ServerJSON(Msg{false, "拒绝访问，你没有权限访问此内容。"}, 403)
			return
		}

		userTokenData := []string{}
		userTokenData = strings.Split(userToken, "=")
		if len(userTokenData) != 2 {
			c.ServerJSON(Msg{false, "访问令牌无效"}, 403)
			return
		}

		token := ""
		originalToken := ""
		userId = userTokenData[1]
		token = userTokenData[0]
		key := "admin_user_" + userId
		originalToken, errMsg = MyRedis.GetData(key)
		if errMsg != "" {
			c.ServerJSON(Msg{false, "redis读取访问令牌发生错误：" + errMsg}, 500)
			return
		}

		// 拦截黑名单用户
		value, errMsg = MyRedis.GetData("blackUser_" + userId)
		if errMsg != "" {
			c.ServerJSON(Msg{false, errMsg}, 500)
			return
		}
		if value != "" {
			c.ServerJSON(Msg{false, "黑名单用户禁止访问！"}, 403)
			return
		}

		if token != originalToken {
			c.ServerJSON(Msg{false, "登录状态已失效，请重新登录。"}, 403)
			return
		}

		uid, _ := strconv.ParseUint(userId, 10, 32)
		c.UserId = uint32(uid)
		cacheKey += "_" + userId
	}

	// 安全检查
	params := r.URL.RawQuery
	if r.Method == "POST" {
		params = string(c.Ctx.Input.RequestBody)
	}
	errMsg = safeCheck("blackIP_"+cacheKey, params)
	if errMsg != "" {
		c.ServerJSON(Msg{false, errMsg}, 400)
		return
	}

	if isLogin {
		return
	}

	// 重置访问令牌的有效期
	key := "admin_user_" + userId
	lifetime := b.BConfig.WebConfig.Session.SessionGCMaxLifetime
	errMsg = MyRedis.SetExpire(key, lifetime*60*60)
	if errMsg != "" {
		c.ServerJSON(Msg{false, errMsg}, 500)
		return
	}

	// 地址栏查询参数：current, pageSize, query
	current, _ := c.GetInt("current", 1)
	pageSize, _ := c.GetInt("pageSize", 20)
	if current <= 0 {
		current = 1
	}
	if pageSize <= 0 {
		pageSize = 20
	}
	if pageSize > 1000 {
		pageSize = 1000
	}
	c.Filter.Current = current
	c.Filter.PageSize = pageSize
	c.Filter.Offset = (current - 1) * pageSize

	queryStr := c.GetString("query", "{}")
	query := map[string]interface{}{}
	err := json.Unmarshal([]byte(queryStr), &query)
	if err != nil {
		logs.Error("查询条件无法解析", err.Error())
		c.ServerJSON(Msg{false, "查询条件无法解析：" + err.Error()}, 400)
	}

	c.Filter.Query = query
}

func (c *BaseC) Error404() {
	c.ServerJSON(Msg{false, "404"}, 404)
}

func (c *BaseC) Error403() {
	c.ServerJSON(Msg{false, "403"}, 403)
}

func (c *BaseC) saveLog(status uint32) {
	r := c.Ctx.Request
	path := strings.Trim(r.URL.Path, "/")
	if path == "api/basic/logger" {
		return
	}

	params := r.URL.RawQuery
	if r.Method == "POST" {
		// 请求主体的内容作为参数
		params = string(c.Ctx.Input.RequestBody)
		if len(params) > 255 {
			params = params[0:255]
		}
	}

	ip := r.RemoteAddr // 舍弃端口号，只要IP地址。
	index := strings.LastIndex(ip, ":")
	ip = ip[0:index]
	atTime := utils.MyTime{time.Now()}
	errMsg := ""
	jsonn, isOk := c.Data["json"].(Msg) // 变量类型断言
	if isOk {
		if !jsonn.Success {
			errMsg = jsonn.Msg
		}
	}
	log := &models.Logger{
		UserId:    c.UserId,
		Ip:        ip,
		UserAgent: r.UserAgent(),
		Path:      path,
		Params:    params,
		Status:    status,
		Method:    r.Method,
		ErrMsg:    errMsg,
		AtTime:    atTime,
	}
	models.Db.Create(log)
}

func (c *BaseC) ServerJSON(data interface{}, status ...uint32) {
	c.Data["json"] = data
	_ = c.ServeJSON()
	var st uint32 = 200
	if len(status) > 0 {
		st = status[0]
	}
	c.saveLog(st)
}

// sql注入检查，临时封ip
func safeCheck(key string, params string) string {
	seconds, errMsg := MyRedis.GetExpire(key)
	if errMsg != "" {
		return errMsg
	}

	if seconds > 1 {
		return "系统繁忙，请于" + utils2.ToString(seconds) + "秒后再试。"
	}

	isDanger := utils.CheckSQLInject(params)
	if !isDanger {
		// 安全的
		return ""
	}

	// 危险的，存在sql注入！
	value := time.Now().Format("05")
	errMsg = MyRedis.SetData(key, value, 100)
	if errMsg != "" {
		return errMsg
	}

	return "请勿尝试SQL注入！"
}

// FormValidation 表单验证
func FormValidation(formData interface{}) (errMsg string) {
	valid := validation.Validation{}
	isOk, err := valid.Valid(formData)
	if err != nil {
		logs.Error(err)
		return err.Error()
	}
	if isOk {
		return ""
	}

	return utils.Translate(valid.Errors[0])
}

func (c *BaseC) ReadData(params ReadParams) (table Table, errMsg string) {
	filter := params.Filter
	if filter.Current == 0 {
		filter.Current = c.Filter.Current
	}
	if filter.PageSize == 0 {
		filter.PageSize = c.Filter.PageSize
	}
	if filter.Offset == 0 {
		filter.Offset = c.Filter.Offset
	}
	if filter.Query == nil {
		filter.Query = c.Filter.Query
	} else {
		// 合并两组查询条件
		query := map[string]interface{}{}
		for key := range filter.Query {
			value := filter.Query[key]
			query[key] = value
		}
		for key := range c.Filter.Query {
			value := c.Filter.Query[key]
			query[key] = value
		}
		filter.Query = query
	}

	// 生成查询条件
	fieldData := []string{}
	valueData := []interface{}{}
	for key := range filter.Query {
		field := key + "=?"
		value := filter.Query[key]
		// 字符串适用于模糊查询
		stringValue, isString := value.(string)
		if isString {
			field = key + " like ?"
			value = "%" + stringValue + "%"
		}
		fieldData = append(fieldData, field)
		valueData = append(valueData, value)
	}
	where := strings.Join(fieldData, " and ")

	table = Table{0, []map[string]interface{}{}}
	var result *gorm.DB
	db := models.Db

	if where == "" {
		result = db.Model(params.Model).Count(&table.Total)
	} else {
		result = db.Model(params.Model).Where(where, valueData...).Count(&table.Total) // 数组解构
	}
	if result.Error != nil {
		return table, "查询记录数量发生错误：" + result.Error.Error()
	}

	if table.Total == 0 {
		return table, ""
	}

	if params.Field == "" {
		params.Field = "*"
	}
	if params.Sort == "" {
		params.Sort = "id desc"
	}

	result = db.Model(params.Model).Select(params.Field)
	if where != "" {
		result = result.Where(where, valueData...)
	}
	if filter.PageSize > 0 {
		// 有些业务场景不需要分页的，传入PageSize:-1即可。
		result = result.Offset(filter.Offset).Limit(filter.PageSize)
	}
	result = result.Order(params.Sort).Find(&table.TableData)

	if result.Error != nil {
		return table, "查询记录发生错误：" + result.Error.Error()
	}

	return table, ""
}
func SaveData(action string, model interface{}, inputData interface{}) (errMsg string) {
	if action != "新增" && action != "编辑" {
		return "参数 action 只接受新增或编辑，其它值无效。"
	}

	db := models.Db
	var result *gorm.DB
	if action == "新增" {
		result = db.Model(model).Create(inputData)
	} else {
		// 编辑
		var id uint32 // 待更新记录的主键
		data, isMap := inputData.(map[string]interface{})
		if isMap {
			id = utils.ToUint32(data["id"])
		}
		result = db.Model(model).Where("id=?", id).Updates(inputData)
	}
	if result.Error == nil {
		return ""
	}

	errMsg = action + "记录发生错误：" + result.Error.Error()
	logs.Error(errMsg)
	if strings.Index(errMsg, "Duplicate entry") > 0 {
		// 友好反馈 Duplicate entry 问题
		msgData := strings.Split(errMsg, "'")
		errMsg = msgData[1] + "已经存在，请换一个。"
	}
	return errMsg
}
func DeleteData(model interface{}, condition string) (errMsg string) {
	db := models.Db
	result := db.Where(condition).Delete(model)
	if result.Error != nil {
		logs.Error("删除记录发生错误：", result.Error)
		return "删除记录发生错误：" + result.Error.Error()
	}

	if result.RowsAffected <= 0 {
		logs.Info("没有删除任何记录")
		return "要删除的记录不存在，请刷新页面。"
	}

	return ""
}

// crud
func (c *BaseC) Read(params ReadParams) {
	page, errMsg := c.ReadData(params)
	if errMsg != "" {
		c.ServerJSON(Msg{false, errMsg}, 500)
		return
	}

	c.ServerJSON(Response{true, page})
}
func (c *BaseC) Create(model interface{}, extraData ...map[string]interface{}) {
	inputData := model
	err := c.BindJSON(&inputData)
	if err != nil {
		logs.Error("JSON解析发生错误：", err)
		c.ServerJSON(Msg{false, "JSON解析发生错误：" + err.Error()}, 500)
		return
	}

	errMsg := FormValidation(inputData)
	if errMsg != "" {
		c.ServerJSON(Msg{false, errMsg}, 400)
		return
	}

	if len(extraData) > 0 {
		// 附加额外的数据(合并两组数据)
		data := map[string]interface{}{}
		_ = c.BindJSON(&data)
		extra := extraData[0]
		for key := range extra {
			value := extra[key]
			data[key] = value
		}
		inputData = data
	}

	errMsg = SaveData("新增", model, inputData)
	if errMsg != "" {
		c.ServerJSON(Msg{false, errMsg}, 500)
		return
	}

	c.ServerJSON(Msg{true, "新增成功"})
}
func (c *BaseC) Update(model interface{}, extraData ...map[string]interface{}) {
	inputData := model
	err := c.BindJSON(&inputData)
	if err != nil {
		logs.Error("BindJSON发生错误：", err)
		c.ServerJSON(Msg{false, "BindJSON发生错误：" + err.Error()}, 500)
		return
	}

	data, isMap := inputData.(map[string]interface{})
	if isMap {
		if len(extraData) > 0 {
			// 附加额外的数据(合并两组数据)
			extra := extraData[0]
			for key := range extra {
				value := extra[key]
				data[key] = value
			}
			inputData = data
		}
	}

	errMsg := SaveData("编辑", model, inputData)
	if errMsg != "" {
		c.ServerJSON(Msg{false, errMsg}, 500)
		return
	}

	c.ServerJSON(Msg{true, "编辑成功"})
}
func (c *BaseC) Del(model interface{}) {
	// 待删除记录的主键，多个主键以逗号分隔
	ids := c.GetString("ids")
	if ids == "" {
		c.ServerJSON(Msg{false, "必须提供要删除记录的主键"}, 400)
		return
	}

	condition := "id in (" + ids + ")"
	errMsg := DeleteData(model, condition)
	if errMsg != "" {
		c.ServerJSON(Msg{false, errMsg}, 500)
		return
	}

	c.ServerJSON(Msg{true, "删除成功"})
}
